Skip to content

FE-1582: Add an in-browser optimization runtime to petrinaut-core - #9515

Draft
kube wants to merge 4 commits into
claude/web-optimizer-python-corefrom
claude/web-optimizer-core-runtime
Draft

FE-1582: Add an in-browser optimization runtime to petrinaut-core#9515
kube wants to merge 4 commits into
claude/web-optimizer-python-corefrom
claude/web-optimizer-core-runtime

Conversation

@kube

@kube kube commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Before this PR, the only optimization capability was the remote one: a host handed Petrinaut a PetrinautOptimization that talked to the Python service, and the service spawned a CLI process to simulate each trial. Running the study anywhere else meant re-implementing the protocol, and the host had no say in how trials were computed.

This PR adds an in-browser optimization runtime to @hashintel/petrinaut-core. A module Web Worker loads Pyodide, installs the pinned Optuna packages and the shared Python study library, and drives the study by ask and tell. Optimizer never simulates: a PetrinautConnectedOptimization is connected to a PetrinautOptimizationChannel the host implements, and every trial goes through that channel. Capability that comes back satisfies the existing PetrinautOptimization contract and adds a connected lifecycle on top: a finished or stopped study stays in the worker until released, so the host can extend it with more trials on the same sampler history, and a run may keep up to four trials in flight.

Links

Changes

Optimization contract

  • PetrinautOptimizationChannel, PetrinautOptimizationTrialRequest and PetrinautOptimizationTrialOutcome describe one trial handed to the host

    Request carries the frozen manifest, the suggested values, every scenario parameter's resolved value, the trial seeds and an abort signal.
    Outcome is an objective or a pruning reason.

  • PetrinautConnectedOptimization and PetrinautOptimizationSource let a host supply either a remote capability or one to connect locally

    isConnectedOptimization tells them apart.

  • PetrinautConnectedOptimizationCapability is what connect returns

    Everything of PetrinautOptimization, plus createOptimizationRun(input, { signal, parallelism }), extendOptimizationRun(runId, trials), releaseOptimizationRun(runId) and dispose().
    PETRINAUT_OPTIMIZATION_MAX_PARALLELISM caps parallelism at 4.

  • optimization/describe.ts owns the describe result, suggested-value validation, trial seed derivation and scenario value resolution

    CLI imports these instead of keeping its own copies; its experiment-based replicate runs keep pinning the same seeds.

Browser runtime

  • browser-optimization entry: createBrowserOptimization returns a connected optimization

    connect(channel) gives a capability with run ids, a per-run event log with dense sequence numbers, replay from a cursor, one study at a time on a shared worker, cancellation and disposal.

  • Run log is a sequence of segments

    Each segment starts with started, carrying the cumulative requestedTrials, and ends with a terminal event.
    Replay from a cursor ends at the first terminal event after it, so a provider re-attaching after Continue sees the new segment only.

  • A finished or stopped study stays in the worker

    complete and a stop leave the run resumable; extendOptimizationRun appends started and queues the next segment on the same study; releaseOptimizationRun drops it.
    Failures finish the run and drop the study.

  • Stale segments cannot touch the next one

    An evaluate posted after a stop is answered as pruned without running; an evaluation of a stopped segment that settles later is ignored.

  • workers/optimizer entry: a module worker that imports pyodide.mjs from the configured index URL, loads the distribution packages, installs Optuna and colorlog with deps=False, writes the Python sources into the virtual filesystem and runs the study

    start creates the study handle and runs the first segment, extend runs the next on the same handle, release drops it.
    Evaluate requests cross to the main thread by request id; cancel resolves a pending evaluate as pruned so the Python loop unwinds.

  • Python sources come from @local/petrinaut-optimizer-core through ?raw imports at build time; versions come from its runtime-lock.json

    Published package has no runtime dependency on the library.

  • Channel failures map to events

    An abort prunes the trial as cancelled, any other throw fails the run with trial_evaluation_failed, a runtime that fails to load fails the run with a retryable optimizer_unavailable and recreates the worker.

  • Attaching to an unknown run throws an error shaped like an HTTP 404, so the provider drops stale session-storage runs silently
  • Library build emits the worker as an asset referenced relative to the importing module

    ?worker&url with type: "module"; the existing inline workers are unchanged.

Known issues

  • Runtime and packages load from jsDelivr and PyPI at first use

    Self-hosting the Pyodide distribution is a follow-up.

Test coverage

  • browser-optimization.test.ts:

    Event sequence and seq, pruned trials, cancellation of running and pending runs, the 404 shape, channel throws and rejections, worker script errors, worker creation failures, study errors, one-at-a-time queueing, cursor replay and abort, disposal, manifest validation.
    Extend after complete and after a stop, rejection of extend on a running or released run and past the trial cap, release of a running run, stopped queued extension, parallelism validation and inheritance, stale evaluations of a stopped segment, an already-aborted signal.

  • study-runner.pyodide.test.ts:

    Real Pyodide under Node: a seeded 30-trial TPE study, a study of 8 then 8 trials that repeats nothing of its first segment while a seeded restart repeats it exactly, stop after 5 evaluations then resume with continued numbering, parallelism 2 with at most 2 in flight, release and failure dropping the study, deterministic suggestions.
    Skips in CI only when the package download fails.

  • run-log.test.ts, pyodide-config.test.ts, optimization/describe.test.ts:

    Log segments, replay and tailing, micropip requirements from the lock, describe result and seed sequence parity with the CLI fixtures.

  • Existing @hashintel/petrinaut-cli suite:

    Unchanged behaviour through the moved helpers.

How to test

  • yarn workspace @hashintel/petrinaut-core test:unit

    Expect the Pyodide suite to download the runtime once and pass

  • yarn workspace @hashintel/petrinaut-core build

    Check dist/assets contains the optimizer worker and dist/browser-optimization.d.ts imports nothing from @local

@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hash Ready Ready Preview Sep 6, 2026 12:29am UTC
petrinaut Ready Ready Preview Sep 6, 2026 12:29am UTC
petrinaut-docs Ready Ready Preview Sep 6, 2026 12:29am UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 6, 2026 12:29am UTC

Request Review

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.89%. Comparing base (52b4835) to head (b597b24).

Additional details and impacted files
@@                        Coverage Diff                        @@
##           claude/web-optimizer-python-core    #9515   +/-   ##
=================================================================
  Coverage                             60.89%   60.89%           
=================================================================
  Files                                  1461     1461           
  Lines                                146725   146725           
  Branches                               6744     6744           
=================================================================
  Hits                                  89343    89343           
  Misses                                56264    56264           
  Partials                               1118     1118           
Flag Coverage Δ
apps.hash-api 15.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from 5d76a1e to f47c52d Compare September 4, 2026 11:23
@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from f47c52d to 01e05ef Compare September 4, 2026 12:02
@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from 01e05ef to 44d1e14 Compare September 4, 2026 12:16
@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from 44d1e14 to 9e5b2fa Compare September 4, 2026 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

1 participant